<meta charset="UTF-8" />

<title>canvas 打字</title>

<div id="canvas_box">
    <canvas></canvas>
    <textarea></textarea>
</div>

<script>
    class BuildInputText {

        constructor(input) {
            this.input = input;
            this.text = "";
            this.InitEvent();
        }

        InitEvent() {
            const { input } = this;

            let incomposition = false;
            const append_input = (value) => {
                this.text += value;
                this.EmitInput();
            };
            const remove_input = () => {
                this.text = this.text.slice(0, -1);
                this.EmitInput();
            };

            const input_compositionstart = () => { incomposition = true; }
            const input_compositionend = () => { incomposition = false; input_oninput(); };
            const input_oninput = () => {
                if (!incomposition) {
                    if (input.value === "\n") { input.value = ""; }
                    else { append_input(input.value); input.value = ""; }
                }
            };
            const input_keydown = (e) => {
                const key = e.key;
                if (key === "Backspace") {
                    remove_input();
                }
            };

            input.addEventListener("keydown", input_keydown);
            input.addEventListener("input", input_oninput);
            input.addEventListener("compositionstart", input_compositionstart);
            input.addEventListener("compositionend", input_compositionend);
        }

        SetPos(x, y) {
            this.input.style.left = x;
            this.input.style.top = y;
        }

        Open(value) {
            this.text = value;
            this.input.focus();
        }

        SetValue(value) { this.text = value; }

        EmitInput() {
            if (this.OnInput) {
                this.OnInput(this.text);
            }
        }

        OnInput(value) { }
    }

    const canvas = canvas_box.querySelector("canvas");
    const _2d = canvas.getContext("2d");
    const inputText = new BuildInputText(canvas_box.querySelector("textarea"));

    const canvas_click = function (e) {
        change_cursor_pos();
        inputText.Open(str);
    };

    canvas.addEventListener("click", canvas_click);

    inputText.OnInput = function (value) {
        str = value;
        change_cursor_pos();
    }

    let x = 100;
    let y = 100;
    const height = 30;
    let cursor = true;
    let cursor_max = 28;
    let cursor_i = 0;
    let str = "初始字符串";
    const font = `800 ${height}px 宋体`;

    const change_cursor_pos = function () {
        _2d.save();
        _2d.font = `800 ${height}px 宋体`;
        const textWidth = _2d.measureText(str).width;
        inputText.SetPos(x + textWidth, y);
        _2d.restore();
    };

    const draw_all = function () {
        canvas.width = canvas.offsetWidth;
        canvas.height = canvas.offsetHeight;

        _2d.save();

        const height = 30;
        _2d.font = `800 ${height}px 宋体`;
        _2d.fillStyle = "#000000";
        _2d.textBaseline = "top";
        const textWidth = _2d.measureText(str).width;
        _2d.fillText(str, x, y);
        if (cursor) {
            _2d.fillRect(x + textWidth + 5, y, 2, height);
        }
        if (cursor_i < cursor_max) {
            cursor_i++;
        } else {
            cursor_i = 0;
            cursor = !cursor;
        }

        _2d.restore();

        requestAnimationFrame(() => {
            draw_all();
        });
    };

    draw_all();
</script>

<style>
    #canvas_box {
        position: relative;
        background-color: #FFFFFF;
        width: 800;
        height: 800;
    }

    #canvas_box>canvas {
        width: 100%;
        height: 100%;
    }

    #canvas_box>textarea {
        position: absolute;
        left: 100;
        top: 100;
        opacity: 0;
        width: 0;
    }
</style>